package com.xbh.common.dao;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import com.xbh.common.model.BaseModel;
import com.xbh.common.model.ModelMetaData;
import jodd.util.StringUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.TransientDataAccessResourceException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;


import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by Doaim on 2017/4/17.
 * 数据库所有字段禁止为null，更新和保存操作所有null值将被忽略
 */
public abstract class BaseDao<T extends BaseModel> {
    private static Logger logger = LogManager.getLogger(BaseDao.class);
    @Autowired
    protected JdbcTemplate jdbcTemplate;

    /**
     * 根据 ID 查找
     *
     * @param ids
     * @return
     */
    public T getById(BeanPropertyRowMapper<T> rowMapper, Object... ids) {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0];
        ModelMetaData metaData = BaseModel.getMetaData(clazz);
        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder.append("select * from " + metaData.getTableName() + " where ");
        ArrayList<String> idWhere = new ArrayList<>();
        for (Map.Entry<Field, String> entry : metaData.getIdFieldColumnMapping().entrySet()) {
            idWhere.add(entry.getValue() + " = ?");
        }
        sqlBuilder.append(StringUtil.join(idWhere.toArray(), " and "));
        if (rowMapper == null) {
            return findOne(sqlBuilder.toString(), new BeanPropertyRowMapper<T>(clazz), ids);
        } else {
            return findOne(sqlBuilder.toString(), rowMapper, ids);
        }
    }

    public T getById(Object... ids) {
        return getById(null, ids);
    }

    /**
     * 保存
     *
     * @param model
     * @return
     */
    public T save(T model) {
        try {
            ModelMetaData metaData = model.getMetaData(model.getClass());
            StringBuilder sqlBuilder = new StringBuilder();
            sqlBuilder.append("insert into " + metaData.getTableName() + " (");
            ArrayList<String> updateFields = new ArrayList<>();
            ArrayList<Object> updateFieldPlaceholders = new ArrayList<>();
            ArrayList<Object> updateFieldValues = new ArrayList<>();
            for (Map.Entry<Field, String> entry : metaData.getFieldColumnMapping().entrySet()) {
                Object fieldValue = entry.getKey().get(model);
                if (fieldValue != null) {
                    updateFields.add(entry.getValue());
                    updateFieldValues.add(parseValue(fieldValue));
                    updateFieldPlaceholders.add("?");
                }
            }
            for (Map.Entry<Field, String> entry : metaData.getIdFieldColumnMapping().entrySet()) {
                updateFields.add(entry.getValue());
                updateFieldValues.add(entry.getKey().get(model));
                updateFieldPlaceholders.add("?");
            }
            sqlBuilder.append(StringUtil.join(updateFields.toArray(), ","));
            sqlBuilder.append(")values(");
            sqlBuilder.append(StringUtil.join(updateFieldPlaceholders.toArray(), ","));
            sqlBuilder.append(")");
            if (logger.isDebugEnabled()) {
                logger.debug(sqlBuilder.toString());
            }
            jdbcTemplate.update(sqlBuilder.toString(), updateFieldValues.toArray());
            return model;
        } catch (IllegalAccessException e) {
            logger.error(e);
            return null;
        }
    }

    ;

    /**
     * 批量保存,不含主键
     *
     * @param models
     * @return
     */
    public void save(List<T> models) {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0];
        ModelMetaData metaData = BaseModel.getMetaData(clazz);

        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder.append("insert into " + metaData.getTableName() + " (");
        ArrayList<String> updateFields = new ArrayList<>();
        ArrayList<Object> updateFieldPlaceholders = new ArrayList<>();
        for (Map.Entry<Field, String> entry : metaData.getFieldColumnMapping().entrySet()) {
            if (entry.getValue() != null) {
                updateFieldPlaceholders.add("?");
                updateFields.add(entry.getValue());
            } else {
                logger.error("列名为空,{}-{}", entry.getKey().getName(), entry.getValue());
            }
        }
        List<Object[]> batchSaveValues = new ArrayList<>();
        for (BaseModel model : models) {
            ArrayList<Object> updateFieldValues = new ArrayList<>();
            metaData.getFieldColumnMapping().forEach((field, columnName) -> {
                try {
                    updateFieldValues.add(parseValue(field.get(model)));
                } catch (IllegalAccessException e) {
                    logger.error(e);
                }
            });
            batchSaveValues.add(updateFieldValues.toArray());
        }

        sqlBuilder.append(StringUtil.join(updateFields.toArray(), ","));
        sqlBuilder.append(")values(");
        sqlBuilder.append(StringUtil.join(updateFieldPlaceholders.toArray(), ","));
        sqlBuilder.append(")");
        if (logger.isDebugEnabled()) {
            logger.debug(sqlBuilder.toString());
        }
        jdbcTemplate.batchUpdate(sqlBuilder.toString(), batchSaveValues);
    }

    /**
     * 批量保存,包含主键
     *
     * @param models
     * @return
     */
    public void saveIdAndField(List<T> models) {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0];
        ModelMetaData metaData = BaseModel.getMetaData(clazz);

        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder.append("insert into " + metaData.getTableName() + " (");
        ArrayList<String> updateFields = new ArrayList<>();
        ArrayList<Object> updateFieldPlaceholders = new ArrayList<>();
        for (Map.Entry<Field, String> entry : metaData.getIdFieldColumnMapping().entrySet()) {
            if (entry.getValue() != null) {
                updateFieldPlaceholders.add("?");
                updateFields.add(entry.getValue());
            } else {
                logger.error("列名为空,{}-{}", entry.getKey().getName(), entry.getValue());
            }
        }
        for (Map.Entry<Field, String> entry : metaData.getFieldColumnMapping().entrySet()) {
            if (entry.getValue() != null) {
                updateFieldPlaceholders.add("?");
                updateFields.add(entry.getValue());
            } else {
                logger.error("列名为空,{}-{}", entry.getKey().getName(), entry.getValue());
            }
        }
        List<Object[]> batchSaveValues = new ArrayList<>();
        for (BaseModel model : models) {
            ArrayList<Object> updateFieldValues = new ArrayList<>();
            metaData.getIdFieldColumnMapping().forEach((field, columnName) -> {
                try {
                    updateFieldValues.add(parseValue(field.get(model)));
                } catch (IllegalAccessException e) {
                    logger.error(e);
                }
            });
            metaData.getFieldColumnMapping().forEach((field, columnName) -> {
                try {
                    updateFieldValues.add(parseValue(field.get(model)));
                } catch (IllegalAccessException e) {
                    logger.error(e);
                }
            });
            batchSaveValues.add(updateFieldValues.toArray());
        }

        sqlBuilder.append(StringUtil.join(updateFields.toArray(), ","));
        sqlBuilder.append(")values(");
        sqlBuilder.append(StringUtil.join(updateFieldPlaceholders.toArray(), ","));
        sqlBuilder.append(")");
        if (logger.isDebugEnabled()) {
            logger.debug(sqlBuilder.toString());
        }
        jdbcTemplate.batchUpdate(sqlBuilder.toString(), batchSaveValues);
    }

    /**
     * 更新 Model
     *
     * @param model
     * @return
     */
    public int update(T model) {
        try {
            ModelMetaData metaData = model.getMetaData(model.getClass());
            StringBuilder sqlBuilder = new StringBuilder();
            sqlBuilder.append("update " + metaData.getTableName() + " set ");
            ArrayList<String> updateFields = new ArrayList<>(metaData.getFieldColumnMapping().size());
            ArrayList<Object> updateFieldValues = new ArrayList<>(metaData.getFieldColumnMapping().size());
            ArrayList<String> idWhere = new ArrayList<>();

            for (Map.Entry<Field, String> entry : metaData.getFieldColumnMapping().entrySet()) {
                Object fieldValue = entry.getKey().get(model);
                if (fieldValue != null) {
                    updateFields.add(entry.getValue() + " = ?");
                    updateFieldValues.add(parseValue(fieldValue));
                }
            }
            for (Map.Entry<Field, String> entry : metaData.getIdFieldColumnMapping().entrySet()) {
                idWhere.add(entry.getValue() + " = ?");
                updateFieldValues.add(entry.getKey().get(model));
            }
            sqlBuilder.append(StringUtil.join(updateFields.toArray(), " , "));
            sqlBuilder.append(" where ");
            sqlBuilder.append(StringUtil.join(idWhere.toArray(), " and "));
            if (logger.isDebugEnabled()) {
                logger.debug(sqlBuilder.toString());
            }
            if (updateFields.isEmpty()) {
                return 0;
            } else {
                return jdbcTemplate.update(sqlBuilder.toString(), updateFieldValues.toArray());
            }
        } catch (IllegalAccessException e) {
            logger.error(e);
            return 0;
        }
    }

    /**
     * 删除
     *
     * @param model
     * @return
     */
    public boolean delete(T model) {
        try {
            ModelMetaData metaData = model.getMetaData(model.getClass());
            StringBuilder sqlBuilder = new StringBuilder();
            sqlBuilder.append("delete from " + metaData.getTableName() + " where ");
            ArrayList<String> idWhere = new ArrayList<>();
            ArrayList<Object> values = new ArrayList<>();
            for (Map.Entry<Field, String> entry : metaData.getIdFieldColumnMapping().entrySet()) {
                idWhere.add(entry.getValue() + " = ?");
                values.add(entry.getKey().get(model));
            }
            sqlBuilder.append(StringUtil.join(idWhere.toArray(), " and "));
            return jdbcTemplate.update(sqlBuilder.toString(), values.toArray()) > 0;
        } catch (IllegalAccessException e) {
            logger.error(e);
            return false;
        }
    }

    ;

    /**
     * 组合主键删除
     *
     * @param ids
     * @return
     */
    public boolean deleteById(Object... ids) {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0];
        ModelMetaData metaData = BaseModel.getMetaData(clazz);
        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder.append("delete from " + metaData.getTableName() + " where ");
        ArrayList<String> idWhere = new ArrayList<>();
        metaData.getIdFieldColumnMapping().forEach((field, column) -> {
            idWhere.add(column + " = ?");
        });
        sqlBuilder.append(StringUtil.join(idWhere.toArray(), " and "));
        return jdbcTemplate.update(sqlBuilder.toString(), ids) > 0;
    }

    public T findOne(String sql, BeanPropertyRowMapper<T> beanPropertyRowMapper, Object... params) {
        T result = null;
        try {
            return jdbcTemplate.queryForObject(sql, beanPropertyRowMapper, params);
        } catch (ConversionFailedException e) {
            throw e;
        } catch (TypeMismatchException e) {
            throw e;
        } catch (TransientDataAccessResourceException e) {
            throw e;
        } catch (EmptyResultDataAccessException e) {
//            e.printStackTrace();
        } catch (Exception e) {
            logger.error(e);
        }
        return result;
    }

    public T findOne(String sql, Object... params) {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0];
        return findOne(sql, new BeanPropertyRowMapper<>(clazz), params);
    }

    public List<T> findList(String sql, Object... params) {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0];
        return findList(sql, new BeanPropertyRowMapper<>(clazz), params);
    }

    public List<T> findList(String sql, BeanPropertyRowMapper beanPropertyRowMapper, Object... params) {
        return jdbcTemplate.query(sql, beanPropertyRowMapper, params);
    }

    public int update(String sql, Object... params) {
        return jdbcTemplate.update(sql, params);
    }

    /**
     * 统计条数
     *
     * @param where
     * @param params
     * @return
     */
    public long count(String where, Object... params) {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0];
        ModelMetaData metaData = BaseModel.getMetaData(clazz);
        return jdbcTemplate.queryForObject("select count(1) from " + metaData.getTableName() + " " + where, params, Long.class);
    }

    private Object parseValue(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof ArrayList) {
            return StringUtil.join(((ArrayList) value).toArray(), ",");
        } else if (value instanceof JSONArray) {
            return ((JSONArray) value).toJSONString();
        } else if (value instanceof JSONObject) {
            return ((JSONObject) value).toJSONString();
        }
        return value;
    }

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }
}
